package com.tian.service.impl.user;

import com.alibaba.fastjson.JSON;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.tian.common.enums.*;
import com.tian.common.util.*;
import com.tian.dto.user.*;
import com.tian.entity.ChargeUser;
import com.tian.entity.GlobalProperty;
import com.tian.entity.SystemBusinessSwitch;
import com.tian.mapper.ChargeUserMapper;
import com.tian.mapper.GlobalPropertyMapper;
import com.tian.mapper.SystemBusinessSwitchMapper;
import com.tian.mq.message.ChargeUserPointMessage;
import com.tian.mq.message.InvitedRegistryMessage;
import com.tian.mq.message.MessageReqIdPrefixConstant;
import com.tian.mq.message.UserBalanceIncrMessage;
import com.tian.mq.producer.ChargeUserPointInitProducer;
import com.tian.mq.producer.InvitedRegistryIncomeProducer;
import com.tian.mq.producer.InvitedRegistryProducer;
import com.tian.service.user.ChargeUserService;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RBitSet;
import org.redisson.api.RBucket;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.DigestUtils;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.Duration;
import java.util.*;


/**
 * {@code @description:} 用户相关操作服务实现类
 *
 * @author tianwc 公众号：Java后端技术全栈
 * 在线刷题 1200+java面试题和1000+篇技术文章：<a href="https://woaijava.cc/">博客地址</a>
 * {@code @date:} 2024/1/18 15:03
 * {@code @version:} 1.0
 */
@Slf4j
@Service
public class ChargeUserServiceImpl implements ChargeUserService {

    private static final String DEFAULT_PASSWORD = "123456";

    @Resource
    private ChargeUserMapper chargeUserMapper;
    @Resource
    private RedissonClient redissonClient;
    @Resource
    private InvitedRegistryProducer invitedRegistryProducer;
    @Resource
    private ChargeUserPointInitProducer chargeUserPointInitProducer;
    @Resource
    private SystemBusinessSwitchMapper systemBusinessSwitchMapper;
    @Resource
    private InvitedRegistryIncomeProducer invitedRegistryIncomeProducer;

    @Resource
    private GlobalPropertyMapper globalPropertyMapper;

    @Override
    public CommonResult<ChargeUserDto> queryById() {
        ChargeUserLoginResDto userInfoCache = UserCacheUtil.getUser();
        Long id = userInfoCache.getId();
        RBucket<String> bucket = redissonClient.getBucket(RedisConstantPre.USER_INFO_ID_PRE + id);
        if (bucket.get() == null) {
            ChargeUser chargeUser = chargeUserMapper.selectByPrimaryKey(id);
            ChargeUserDto chargeUserDto = ChargeUserConvert.convert(chargeUser);

            ChargeUserLoginResDto chargeUserLoginResDto = new ChargeUserLoginResDto();
            BeanUtils.copyProperties(chargeUser, chargeUserLoginResDto);
            bucket.set(JSON.toJSONString(chargeUserLoginResDto));

            return CommonResult.success(chargeUserDto);
        }
        ChargeUser chargeUser = JSON.parseObject(bucket.get(), ChargeUser.class);
        ChargeUserDto chargeUserDto = ChargeUserConvert.convert(chargeUser);
        return CommonResult.success(chargeUserDto);
    }

    @Override
    public CommonResult<Boolean> register(ChargeUserRegistryReqDto chargeUserRegistryReqDto) {
        String phone = chargeUserRegistryReqDto.getPhone();
        String invitedCode = chargeUserRegistryReqDto.getInvitedCode();
        ChargeUser chargeUser = null;
        if (!StringUtil.isBlank(invitedCode)) {
            //todo 优化---缓存优化，先查询缓存，再查询数据库
            chargeUser = chargeUserMapper.queryByInvitedCode(invitedCode);
            if (chargeUser == null) {
                log.error("邀请码不存在 code = {} ", invitedCode);
                return CommonResult.failed(ResultCode.INVITED_CODE_NO_EXIST);
            }
        }
        //校验手机号是否已经被注册过
        ChargeUser dbUser = chargeUserMapper.selectByPhone(phone);
        if (dbUser != null) {
            return CommonResult.failed(ResultCode.USER_EXISTED);
        }
        int sendPoint = 0;
        //获取全局配置属性，送多少积分依据系统配置来
        GlobalProperty globalPropertyRegistry = globalPropertyMapper.selectByBusiType(GlobalPropertyTypeEnums.REGISTRY_SEND_POINT.getType());
        if (globalPropertyRegistry != null) {
            sendPoint = globalPropertyRegistry.getBusiDefault();
        }
        Date current = new Date();

        //用户信息初始化
        ChargeUser user = new ChargeUser();
        user.setPhone(phone);
        user.setStatus(UserStatusEnum.INIT.getStatus());
        user.setAuthentication(UserAuthenticationEnum.INIT.getStatus());
        user.setVip(UserVipEnum.NORMAL.getStatus());
        user.setCreateTime(current);
        user.setNickName(chargeUserRegistryReqDto.getUserName());
        user.setEmail(chargeUserRegistryReqDto.getEmail());
        user.setInviteCode(ShareCodeUtils.idToCode(Long.parseLong(phone)));
        user.setType(0);
        String password = chargeUserRegistryReqDto.getPassword();
        if (StringUtil.isBlank(password)) {
            password = DEFAULT_PASSWORD;
        }
        user.setPassword(DigestUtils.md5DigestAsHex(password.getBytes()));
        chargeUserMapper.insert(user);

        //初始化用户积分账户
        sendMessage2InitUserPoint(user, sendPoint);

        //如果没有邀请码，后面的流程就不用再走了
        if (StringUtil.isBlank(invitedCode)) {
            return CommonResult.success(Boolean.TRUE);
        }
        //系统业务开关---邀请注册开关是否开启
        SystemBusinessSwitch systemBusinessSwitch = systemBusinessSwitchMapper.selectByType(SystemBusinessSwitchTypeEnum.INVITED_REGISTRY_INCOME.getType());
        if (systemBusinessSwitch != null && systemBusinessSwitch.getOpen() == SystemBusinessSwitchOpenEnum.CLOSE.getType()) {
            log.info("业务type={} 名称={} 已开关已关闭", systemBusinessSwitch.getType(), systemBusinessSwitch.getDesc());
            return CommonResult.success(Boolean.TRUE);
        }

        BigDecimal income = BigDecimal.ZERO;
        //系统邀请收益多少，由系统运营人员配置
        GlobalProperty globalPropertyInvited = globalPropertyMapper.selectByBusiType(GlobalPropertyTypeEnums.INVITED_REGISTRY_INCOME.getType());
        if (globalPropertyInvited != null) {
            income = new BigDecimal(globalPropertyInvited.getBusiDefault());
            sendMessage2InvitedIncome(user.getId(), income, chargeUser.getId());
        }
        sendMessage2InvitedRegistry(income, chargeUser.getId(), user.getId(), current);
        return CommonResult.success(Boolean.TRUE);
    }

    /**
     * 邀请用户注册成功，获取收益，发送消息异步处理
     *
     * @param newUserId 新用户id
     * @param income    收益
     * @param oldUserId 老用户id
     */
    private void sendMessage2InvitedIncome(Long newUserId, BigDecimal income, Long oldUserId) {
        UserBalanceIncrMessage userBalanceIncrMessage = new UserBalanceIncrMessage();
        userBalanceIncrMessage.setUserId(oldUserId);
        userBalanceIncrMessage.setChangeBalance(income);
        userBalanceIncrMessage.setChangeType(UserBalanceChangeTypeEnum.USER_INVITE_USER.getType());
        userBalanceIncrMessage.setRemark("邀请用户注册奖励,新用户id=" + newUserId);
        userBalanceIncrMessage.setReqId(MessageReqIdPrefixConstant.USER_INVITED_REGISTRY_INCOME_REQ_ID_PREFIX + UUID.randomUUID() + DateUtils.formatDefaultDateMs());

        invitedRegistryIncomeProducer.send(userBalanceIncrMessage);
    }

    /**
     * 用户注册成功，新增邀请记录，发送消息异步处理
     *
     * @param income    收益
     * @param oldUserId 老用户id
     * @param newUserId 新用户id
     * @param current   当前时间
     */
    private void sendMessage2InvitedRegistry(BigDecimal income, Long oldUserId, Long newUserId, Date current) {
        InvitedRegistryMessage invitedRegistryMessage = new InvitedRegistryMessage();
        invitedRegistryMessage.setAmount(income);
        invitedRegistryMessage.setUserId(oldUserId);
        invitedRegistryMessage.setNewUserId(newUserId);
        invitedRegistryMessage.setCreateTime(current);
        invitedRegistryMessage.setReqId(MessageReqIdPrefixConstant.USER_INVITED_REGISTRY_REQ_ID_PREFIX + UUID.randomUUID() + DateUtils.formatDefaultDateMs());
        invitedRegistryProducer.send(invitedRegistryMessage);
    }

    /**
     * 初始化用户积分账户
     */
    private void sendMessage2InitUserPoint(ChargeUser user, int sendPoint) {
        ChargeUserPointMessage chargeUserPointMessage = new ChargeUserPointMessage();
        chargeUserPointMessage.setUserId(user.getId());
        chargeUserPointMessage.setPoint(sendPoint);
        chargeUserPointMessage.setReqId(MessageReqIdPrefixConstant.USER_POINT_INIT_REQ_ID_PREFIX + UUID.randomUUID() + DateUtils.formatDefaultDateMs());
        chargeUserPointInitProducer.send(chargeUserPointMessage);
    }

    @Override
    public CommonResult<Boolean> updateNickNameById(ChargeUserDto chargeUserDto) {
        if (StringUtil.isBlank(chargeUserDto.getNickName())) {
            return CommonResult.failed(ResultCode.PARAMETER_EMPTY);
        }
        ChargeUser chargeUser = getChargeUser();
        chargeUser.setNickName(chargeUserDto.getNickName());
        chargeUserMapper.updateById(chargeUser);
        RBucket<String> bucket = redissonClient.getBucket(RedisConstantPre.USER_INFO_ID_PRE + chargeUser.getId());

        ChargeUserLoginResDto chargeUserLoginResDto = new ChargeUserLoginResDto();
        BeanUtils.copyProperties(chargeUserMapper.selectByPrimaryKey(chargeUser.getId()), chargeUserLoginResDto);
        bucket.set(JSON.toJSONString(chargeUserLoginResDto));

        return CommonResult.success(Boolean.TRUE);
    }

    @Override
    public CommonResult<Boolean> updateAvatarImgById(ChargeUserDto chargeUserDto) {
        if (StringUtil.isBlank(chargeUserDto.getAvatarImg())) {
            return CommonResult.failed(ResultCode.PARAMETER_EMPTY);
        }
        ChargeUser chargeUser = getChargeUser();
        chargeUser.setAvatarImg(chargeUserDto.getAvatarImg());
        chargeUserMapper.updateById(chargeUser);
        RBucket<String> bucket = redissonClient.getBucket(RedisConstantPre.USER_INFO_ID_PRE + chargeUser.getId());

        ChargeUserLoginResDto chargeUserLoginResDto = new ChargeUserLoginResDto();
        BeanUtils.copyProperties(chargeUserMapper.selectByPrimaryKey(chargeUser.getId()), chargeUserLoginResDto);
        bucket.set(JSON.toJSONString(chargeUserLoginResDto));

        return CommonResult.success(Boolean.TRUE);
    }

    private static ChargeUser getChargeUser() {
        ChargeUserLoginResDto userInfoCache = UserCacheUtil.getUser();
        Long id = userInfoCache.getId();
        ChargeUser chargeUser = new ChargeUser();
        chargeUser.setId(id);
        return chargeUser;
    }

    @Override
    public CommonResult<ChargeUsersRespDto> findUserByUserIdList(ChargeUsersReqDto chargeUsersReqDto) {
        List<ChargeUser> chargeUserList = chargeUserMapper.selectByIdList(chargeUsersReqDto.getUserIdList());
        ChargeUsersRespDto chargeUsersRespDto = new ChargeUsersRespDto();
        if (!CollectionUtils.isEmpty(chargeUserList)) {
            List<ChargeUserInfoDto> chargeUserInfoDtoList = new ArrayList<>();
            for (ChargeUser chargeUser : chargeUserList) {
                ChargeUserInfoDto chargeUserInfoDto = new ChargeUserInfoDto();
                BeanUtils.copyProperties(chargeUser, chargeUserInfoDto);
                chargeUserInfoDtoList.add(chargeUserInfoDto);
            }
            chargeUsersRespDto.setChargeUserInfoDtoList(chargeUserInfoDtoList);
        }
        return CommonResult.success(chargeUsersRespDto);
    }

    @Override
    public CommonResult<UserCountRespDto> userCount() {
        UserCountRespDto userCountRespDto = new UserCountRespDto();
        long registryUserCount = chargeUserMapper.count();
        userCountRespDto.setRegistryUserCount(registryUserCount);
        userCountRespDto.setActiveUserCount(0L);
        return CommonResult.success(userCountRespDto);
    }

    @Override
    public CommonResult<PageResult<List<ChargeUserInfoDto>>> list(ChargeUserPageReqDto chargeUserPageReqDto) {
        Page<ChargeUser> pageable = PageHelper.startPage(chargeUserPageReqDto.getCurrentPage(), chargeUserPageReqDto.getPageSize());
        List<ChargeUser> chargeUserList = chargeUserMapper.selectByPage(chargeUserPageReqDto);
        List<ChargeUserInfoDto> chargeUserInfoDtoList = new ArrayList<>();
        String urlPre = "http://tianwc.oss-cn-beijing.aliyuncs.com/dev";
        for (ChargeUser chargeUser : chargeUserList) {
            ChargeUserInfoDto chargeUserInfoDto = new ChargeUserInfoDto();
            BeanUtils.copyProperties(chargeUser, chargeUserInfoDto);
            if (!StringUtil.isBlank(chargeUser.getIdCardBack())) {
                chargeUserInfoDto.setIdCardBack(urlPre + chargeUser.getIdCardBack());
                chargeUserInfoDto.setIdCardFront(urlPre + chargeUser.getIdCardFront());
            }
            chargeUserInfoDto.setCreateTimeStr(DateUtils.format(chargeUser.getCreateTime(), DateUtils.FORMAT_SECOND));
            chargeUserInfoDtoList.add(chargeUserInfoDto);
        }
        int pages = pageable.getPages();
        long total = pageable.getTotal();
        PageResult<List<ChargeUserInfoDto>> pageResult = new PageResult<>((int) total,
                chargeUserPageReqDto.getPageSize(),
                pages, chargeUserPageReqDto.getCurrentPage(), chargeUserInfoDtoList);
        return CommonResult.success(pageResult);
    }

    @Override
    public CommonResult<ManagementUserCountRespDto> user7DayCount() {
        ManagementUserCountRespDto managementUserCountRespDto = new ManagementUserCountRespDto();

        List<String> dayActiveList = new ArrayList<>();
        List<Long> userActiveCountList = new ArrayList<>();
        for (int i = 6; i >= 0; i--) {
            Date date = DateUtils.addDay(new Date(), -i);
            String format = DateUtils.format(date, DateUtils.FORMAT_NO_DATE);
            String bitSetKey = RedisConstantPre.USER_ONLINE_COUNT_PRE + format;
            dayActiveList.add(format);
            userActiveCountList.add(redissonClient.getBitSet(bitSetKey).cardinality());
        }

        List<User7DayRegistryTotalRespDto> user7DayActiveTotalRespList = chargeUserMapper.countByDays();
        List<Long> userReistryCountList = new ArrayList<>();
        List<String> dayReistryList = new ArrayList<>();

        Map<String, Long> userReistryCountMap = new HashMap<>();
        user7DayActiveTotalRespList.forEach(user7DayRegistryTotalRespDto -> {
            userReistryCountMap.put(DateUtils.format(user7DayRegistryTotalRespDto.getDate(), DateUtils.FORMAT_NO_DATE), user7DayRegistryTotalRespDto.getUserCount());
        });
        for (int i = 6; i >= 0; i--) {
            Date date = DateUtils.addDay(new Date(), -i);
            String format = DateUtils.format(date, DateUtils.FORMAT_NO_DATE);
            if (userReistryCountMap.containsKey(format)) {
                userReistryCountList.add(userReistryCountMap.get(format));
                dayReistryList.add(format);
                continue;
            }
            userReistryCountList.add(0L);
            dayReistryList.add(format);
        }
        managementUserCountRespDto.setDayReistryList(dayReistryList);
        managementUserCountRespDto.setDayActiveList(dayActiveList);
        managementUserCountRespDto.setUserReistryCountList(userReistryCountList);
        managementUserCountRespDto.setUserActiveCountList(userActiveCountList);

        return CommonResult.success(managementUserCountRespDto);
    }

    @Override
    public CommonResult<Long> onlineUserCount(OnlineUserQueryReqDto onlineUserQueryReqDto) {
        int days = onlineUserQueryReqDto.getDays();
        Boolean isContainTodayOnline = onlineUserQueryReqDto.getIsContainTodayOnline();
        RBitSet onlineUser;
        if (days == 0) {
            onlineUser = redissonClient.getBitSet(RedisConstantPre.USER_ONLINE_COUNT_PRE + DateUtils.formatCurrentDay());
            return CommonResult.success(onlineUser.cardinality());
        }

        String[] bitSetKeys = new String[isContainTodayOnline ? days + 1 : days];
        for (int i = 1; i <= days; i++) {
            Date date = DateUtils.addDay(new Date(), -i);
            //前三天的key
            bitSetKeys[i - 1] = RedisConstantPre.USER_ONLINE_COUNT_PRE + DateUtils.format(date, DateUtils.FORMAT_NO_DATE);
        }
        if (isContainTodayOnline) {
            bitSetKeys[days - 1] = RedisConstantPre.USER_ONLINE_COUNT_PRE + DateUtils.formatCurrentDay();
        }
        onlineUser = redissonClient.getBitSet(RedisConstantPre.USER_ONLINE_COUNT_PRE + DateUtils.formatCurrentDay() + "_" + days);
        onlineUser.or(bitSetKeys);
        //把统计这个key设置过期时间维一天
        Duration duration = Duration.ofDays(1);
        onlineUser.expire(duration);

        return CommonResult.success(onlineUser.cardinality());
    }

    @Override
    public CommonResult<Boolean> auth(AuthUserReqDto authUserReqDto) {
        chargeUserMapper.updateAuthenticationById(authUserReqDto);
        RBucket<String> bucket = redissonClient.getBucket(RedisConstantPre.USER_INFO_ID_PRE + authUserReqDto.getUserId());

        ChargeUserLoginResDto chargeUserLoginResDto = JSON.parseObject(bucket.get(), ChargeUserLoginResDto.class);
        chargeUserLoginResDto.setAuthentication(authUserReqDto.getAuthentication());
        bucket.set(JSON.toJSONString(chargeUserLoginResDto));
        return CommonResult.success(Boolean.TRUE);
    }

    @Override
    public CommonResult<Boolean> submitAuthInfo(UserIdCardReqDto chargeUserDto) {
        if (StringUtil.isBlank(chargeUserDto.getIdCardBack()) || StringUtil.isBlank(chargeUserDto.getIdCardFront())) {
            return CommonResult.failed(ResultCode.PARAMETER_EMPTY);
        }
        ChargeUserLoginResDto userInfoCache = UserCacheUtil.getUser();
        chargeUserDto.setUserId(userInfoCache.getId());
        chargeUserMapper.updateIdCardImgById(chargeUserDto);
        RBucket<String> bucket = redissonClient.getBucket(RedisConstantPre.USER_INFO_ID_PRE + userInfoCache.getId());

        ChargeUserLoginResDto chargeUserLoginResDto = JSON.parseObject(bucket.get(), ChargeUserLoginResDto.class);
        chargeUserLoginResDto.setAuthentication(UserAuthenticationEnum.UNCHECKED.getStatus());
        bucket.set(JSON.toJSONString(chargeUserLoginResDto));
        return CommonResult.success(Boolean.TRUE);
    }
}
